#!/usr/bin/python
# -*- coding: utf-8 -*-

'''
    Copyright 2009, The Android Open Source Project

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
'''

# script to highlight adb logcat output for console
# written by jeff sharkey, http://jsharkey.org/
# piping detection and popen() added by other android team members
# multiple logcat format support added by rolf schroder


import os
import signal
import sys
import re
import platform
import threading
import getopt
import time
import datetime
import subprocess
if sys.version_info.major == 2:
    # Python2
    import StringIO
else:
    # Python3
    from io import StringIO

WHITE = "38;5;231m"
BLUE = "38;5;75m"
GREEN = "38;5;40m"
YELLOW = "38;5;166m"
RED = "38;5;196m"

TAG_WIDTH = 20
TAGTYPE2COLOR = {
    "V": WHITE,
    "D": BLUE,
    "I": GREEN,
    "W": YELLOW,
    "E": RED,
}

TAGTYPE2LEVEL = {
    "V": 1,
    "D": 2,
    "I": 3,
    "W": 4,
    "E": 5,
}


def format(fg=None, reset=False):
    # manually derived from http://en.wikipedia.org/wiki/ANSI_escape_code#Codes
    codes = []
    if reset:
        codes.append("0m")
    else:
        if fg is not None:
            codes.append("%s" % (fg))
    return "\033[%s" % (";".join(codes))


def indent_wrap(message, indent=0, width=80):
    wrap_area = width - indent
    messagebuf = StringIO.StringIO()
    current = 0
    while current < len(message):
        next = min(current + wrap_area, len(message))
        messagebuf.write(message[current:next])
        if next < len(message):
            messagebuf.write("\n%s" % (" " * indent))
        current = next
    return messagebuf.getvalue()


def tagtype2color(tagtype):
    return TAGTYPE2COLOR[tagtype]


def filter_log_level(tagtype, filter_level):
    try:
        return TAGTYPE2LEVEL[tagtype] < TAGTYPE2LEVEL[filter_level]
    except:
        pass


def timestamp_convert(timestamp):
    try:
        m_datetime = datetime.datetime.strptime(
            str(datetime.date.today())+" "+timestamp, '%Y-%m-%d %H:%M:%S.%f')
        m_timestamp = int(time.mktime(m_datetime.utctimetuple())
                          * 1000 + m_datetime.microsecond / 1000)
        global g_diff_time
        if (g_diff_time == 0):
            g_diff_time = int(round(time.time() * 1000)) - m_timestamp
        return m_timestamp + g_diff_time
    except:
        return sys.maxsize


class collectLogThread (threading.Thread):
    def __init__(self, arg_command, filter_level):
        threading.Thread.__init__(self)
        self.arg_command = arg_command
        self.filter_level = filter_level

    def run(self):
        colored_print_log(self.arg_command, self.filter_level)


def colored_print_log(arg_command, filter_level):
    # 08-29 11:32:28.839 D/dalvikvm( 7497): GC_CONCURRENT freed 1976K, 73% ...
    re_time = re.compile('^(\d*-\d* \d*:\d*:\d*\.\d*):? '
                         '([A-Z])/(.*)\(\s*(\d*)\): (.*)$')

    # D/dalvikvm( 7497): GC_CONCURRENT freed 1976K, 73% free 3084K/11380K, ...
    re_brief = re.compile("^([A-Z])/(.*)\(\s*(\d*)\): (.*)$")

    # 08-29 13:35:56.819  1052  1052 D StatusBar.NetworkController: mDataCo...
    re_threadtime = re.compile('^\d*-\d* (\d*:\d*:\d*\.\d*)\s*(\d*)\s*(\d*) '
                               '([A-Z]) (.*): (.*)$')

    # E 09-25 15:16:59.777 xxxxxxxxxxxxxxxxx170(22811): yyyyyyyyyyyy...
    re_self = re.compile('^([A-Z]) (\d*-\d* \d*:\d*:\d*\.\d*) '
                         '(.*)\(\s*(\d*)\): (.*)$')

    # ... [xxx] 02:51:21.216 [D] xxxxxxxx ...
    re_tee = re.compile('.*\[(.*)\] (\d*:\d*:\d*\.\d*) \[(.)\].*$')

    # ...02:51:21.216 [xxx][xxx][D]: ...
    re_tee_new = re.compile('.*(\d{2}:\d{2}:\d{2}\.\d{3}) (.*)\]\[(.)\]:.*$')

    # if someone is piping in to us, use stdin as input. else invoke adb logcat
    if os.isatty(sys.stdin.fileno()):
        key_word = "ANC_"
        if(platform.system() == 'Windows'):
            grep_command = ""
        else:
            grep_command = " | stdbuf -oL grep " + key_word

        if arg_command.endswith("logcat"):
            input = os.popen("adb shell logcat" + grep_command)
        elif arg_command.endswith("logcatee"):
            out = os.popen("adb shell getprop ro.hardware").read()
            if out.startswith("mt"):
                input = os.popen(
                    "adb shell cat /dev/kmsg" + grep_command)
            elif out.startswith("qcom"):
                input = os.popen(
                    "adb shell cat /proc/tzdbg/qsee_log" + grep_command)
            else:
                input = os.popen(
                    "adb shell cat /dev/kmsg" + grep_command)
    else:
        input = sys.stdin
    global is_exit
    while not is_exit:
        try:
            line = input.readline()
        except:
            pass
        if line is None or line == "":
            break
        if(platform.system() == 'Windows'):
            if key_word not in line:
                continue

        mbrief = re_brief.match(line)
        mtime = re_time.match(line)
        mthreadtime = re_threadtime.match(line)
        mself = re_self.match(line)
        tee = re_tee.match(line)
        tee_new = re_tee_new.match(line)
        if mbrief:
            tagtype, tag, pid, message = mbrief.groups()
            timestamp = None
        elif mtime:
            timestamp, tagtype, tag, pid, message = mtime.groups()
        elif mthreadtime:
            timestamp, pid, _, tagtype, tag, message = mthreadtime.groups()
        elif mself:
            tagtype, timestamp, tag, pid, message = mself.groups()
        elif tee:
            tag, timestamp, tagtype = tee.groups()
            line = line[line.index(tag) - 2:]
            line = "      " + line
        elif tee_new:
            timestamp, tag, tagtype = tee_new.groups()
            line = "      " + timestamp + \
                "               " + line[line.index(tag):]

        elif len(line) == 0:
            break
        else:
            tag = ""
            tagtype = "V"

        if filter_log_level(tagtype, filter_level):
            continue

        if sys.version_info.major == 2:
            # Python2
            linebuf = StringIO.StringIO()
        else:
            # Python3
            linebuf = StringIO()
        color = tagtype2color(tagtype)
        linebuf.write(format(fg=color))
        linebuf.write("%s" % line)
        linebuf.write(format(reset=True))
        line = linebuf.getvalue()
        log_queue.enqueue(Log(timestamp_convert(timestamp), line))

    log_queue.semaphore_release()
    signal_handler(None, None)


class printLogThread (threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        global is_exit
        while not is_exit:
            try:
                log_semaphore.acquire()
                log = log_queue.dequeue()
                if log is not None:
                    sys.stdout.write(log.content)
                    sys.stdout.flush()
            except KeyboardInterrupt:
                break


class Log(object):
    def __init__(self, timestamp, content):
        self.timestamp = timestamp
        self.content = content


class Node(object):
    def __init__(self, elem, ahead=None, next=None):
        self.ahead = ahead  # 表示上一个链接的链点
        self.elem = elem  # 表示对应的元素值
        self.next = next  # 表示下一个链接的链点


class Queue(object):
    def __init__(self):
        self.head = None  # 头部链点为 None
        self.rear = None  # 尾部链点为 None
        self.lock = threading.RLock()

    def is_empty(self):
        return self.head is None  # 判断队列是否为空

    def insert_node(self, rear, p):
        temp = rear
        while temp.ahead is not None and p.elem.timestamp < temp.elem.timestamp:
            temp = temp.ahead
        p.next = temp.next
        p.ahead = temp
        temp.next = p
        if p.next is not None:
            p.next.ahead = p

    def semaphore_release(self):
        log_semaphore.release()

    def enqueue(self, elem):
        """
        往队尾添加一个元素
        :param elem:
        :return:
        """
        self.lock.acquire()
        p = Node(elem)  # 初始化一个新的点
        if self.is_empty():
            self.head = p  # 队列头部为新的链点
            self.rear = p  # 队列尾部为新的链点
        else:
            if p.elem.timestamp < self.rear.elem.timestamp:
                self.insert_node(self.rear, p)
            else:
                p.ahead = self.rear  # 这个新的点的前节点是当前的尾部
                self.rear.next = p  # 队列尾部的后继是这个新的点
                self.rear = p  # 然后让队列尾部指针指向这个新的点

        # log 缓存0.1s
        t = threading.Timer(0.1, self.semaphore_release)
        t.start()

        self.lock.release()

    def dequeue(self):
        """
        从队列头部删除一个元素，并返回这个值，类似于pop
        :return:
        """
        if self.is_empty():  # 判断队列是否为空
            pass
            # print('Queue_is_empty')  # 若队列为空，则退出 dequeue 操作
        else:
            self.lock.acquire()
            result = self.head.elem  # result为队列头部元素
            self.head = self.head.next  # 改变队列头部指针位置
            self.lock.release()
            return result  # 返回队列头部元素

    def peek(self):
        """
        查看队列的队头
        :return:
        """
        if self.is_empty():  # 判断队列是否为空
            print('NOT_FOUND')  # 为空则返回 NOT_FOUND
        else:
            return self.head.elem  # 返回队列头部元素


def signal_handler(signum, frame):
    global is_exit
    is_exit = True
    if(platform.system() == 'Windows' and signum == signal.SIGINT):
        print(" \nDouble Ctrl + C exit!")
    time.sleep(0.1)
    os._exit(0)


signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)

subprocess.call(["adb", "wait-for-device"])
subprocess.call(["adb", "root"])
subprocess.call(["adb", "wait-for-device"])
subprocess.call(["adb", "remount"])
subprocess.call(["adb", "wait-for-device"])
subprocess.call(["adb", "shell", "setenforce", "0"])
subprocess.call(["adb", "wait-for-device"])

if(platform.system() == 'Windows'):
    arg_command = sys.argv[1]
else:
    arg_command = sys.argv[0]
filter_level = "D"

argv = sys.argv[1:]
try:
    opts, args = getopt.getopt(argv, "c:l:")
except:
    print("Error")
    sys.exit(2)
for opt, arg in opts:
    if opt in ['-c']:
        arg_command = arg
    elif opt in ['-l']:
        filter_level = arg

is_exit = False
g_diff_time = 0
log_semaphore = threading.Semaphore(0)
log_queue = Queue()

if arg_command.endswith("logall"):
    thread1 = collectLogThread("logcat", filter_level)
    thread1.setDaemon(True)
    thread1.start()
    thread2 = collectLogThread("logcatee", filter_level)
    thread2.setDaemon(True)
    thread2.start()
else:
    thread = collectLogThread(arg_command, filter_level)
    thread.setDaemon(True)
    thread.start()
printThread = printLogThread()
printThread.setDaemon(True)
printThread.start()

while printThread.is_alive():
    time.sleep(1000)
